package edu.washington.ling.syntric.consistency;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import edu.washington.ling.syntric.struct.tree.Token;
import edu.washington.ling.syntric.struct.tree.Tree;
import edu.washington.ling.syntric.util.TreeUtils;

class AttachmentOrder {
	private String ecType, direction;
	private boolean headAdjacent;
	
	public AttachmentOrder(String ecType, String direction, boolean headAdjacent) {
		this.ecType = ecType;
		this.direction = direction;
		this.headAdjacent = headAdjacent;
	}
	public String getKey() {
		return String.format(ecType);
	}
	public String getDirection() {
		return direction;
	}
	public void attachEmptyCategory(Token ec, Token head, Tree ds) {
		if (headAdjacent) {
			OrderUtils.attachAdjacentToHeadToken(ec, head, ds, direction);
		}
		else {
			OrderUtils.attachAdjacentToHeadSpan(ec, head, ds, direction);
		}
	}
}

class CanonicalOrder {
	
	private String headTag, relType, direction;
	private Set<String> outerRelationSet, innerRelationSet;
	private boolean headAdjacent;
	
	public CanonicalOrder(String headTag, String relType, String direction, 
			String[] outerRelations, String[] innerRelations) {
		this(headTag, relType, direction, outerRelations, innerRelations, false);
	}
	public CanonicalOrder(String headTag, String relType, String direction, 
			String[] outerRelations, String[] innerRelations, boolean headAdjacent) {
		this.relType = relType;
		this.headTag = headTag;
		this.direction = direction;
		this.outerRelationSet = new HashSet<String>(Arrays.asList(outerRelations));
		this.innerRelationSet = new HashSet<String>(Arrays.asList(innerRelations));
		this.headAdjacent = headAdjacent;
	}
	public String getKey() {
		return String.format("%s__%s", relType, headTag);
	}
	public String getDirection() {
		return direction;
	}
	
	public void attachEmptyCategory(Token ec, Token head, Tree ds) {
		boolean ecAttached = false;
		List<Token> children = new LinkedList<Token>();
		if (direction.equals("left")) {
			children = TreeUtils.getLeftDSChildren(head, ds);
			TreeUtils.sortTokens(children, ds);
		} else {
			children = TreeUtils.getRightDSChildren(head, ds);
			TreeUtils.sortTokens(children, ds, true);
		}
		for (Token child: children) {
			if (OrderUtils.isSuitableAnchor(child) && innerRelationSet.contains(child.getDSRelation())) {
				OrderUtils.attachAdjacentToSiblingSpan(ec, child, head, ds, direction);
				ecAttached = true;
				break;
			}
		}
		if (ecAttached == false) {
			if (headAdjacent) {
				OrderUtils.attachAdjacentToHeadToken(ec, head, ds, direction);
				ecAttached = true;
			}
		}
		if (ecAttached == false) {
			for (Token child: children) {
				if (OrderUtils.isSuitableAnchor(child) && !outerRelationSet.contains(child.getDSRelation())) {
					OrderUtils.attachAdjacentToSiblingSpan(ec, child, head, ds, direction);
					ecAttached = true;
					break;
				}
			}
		}
		if (ecAttached = false) {
			OrderUtils.attachAdjacentToHeadToken(ec, head, ds, direction);
		}
		
	}
	public boolean violatesHeadOrder(Token dep, Token head, Tree ds) {
		int headDist = ds.getTokenPosition(head) - ds.getTokenPosition(dep);
		boolean violatesHeadOrder = false;
		if (direction.equals("left") && headDist < 0) {
			violatesHeadOrder = true;
		} else if (direction.equals("right") && headDist > 0) {
			violatesHeadOrder = true;
		}
		return violatesHeadOrder;
	}
	public boolean violatesArgumentOrder(Token dep, Token head, Tree ds) {
		boolean violatesOrder = false;
		List<Token> innerSiblings = OrderUtils.getInnerSiblings(dep, head, ds, direction);
		for (Token innerSibling: innerSiblings) {
			if (outerRelationSet.contains(innerSibling.getDSRelation())) {
				violatesOrder = true;
				break;
			}
		}
		return violatesOrder;
	}
	
	public boolean violatesAdjacency(Token dep, Token head, Tree ds) {
		boolean violatesAdjacency = false;
		List<Token> innerSiblings = OrderUtils.getInnerSiblings(dep, head, ds, direction);
		if (innerSiblings.size() > 0) {
			if (innerRelationSet.size() > 0 && headAdjacent == true) {
				if (!innerRelationSet.contains(innerSiblings.get(0).getDSRelation())) {
					violatesAdjacency = true;
				}
			}
			else if (innerRelationSet.size() > 0 && headAdjacent == false) {
				if (!innerRelationSet.contains(innerSiblings.get(0).getDSRelation())) {
					for (Token innerSibling: innerSiblings.subList(1, innerSiblings.size())) {
						if(innerRelationSet.contains(innerSibling.getDSRelation())) {
							violatesAdjacency = true;
							break;
						}
					}
				}
			}
		}
		return violatesAdjacency;
	}
	 
}

public class OrderModule {
	
	private Map<String, AttachmentOrder> atcOrderMap = new HashMap<String, AttachmentOrder>();
	private Map<String, CanonicalOrder> canOrderMap = new HashMap<String, CanonicalOrder>();
	
	public OrderModule() {
		List<AttachmentOrder> atcOrders = new LinkedList<AttachmentOrder>();
		atcOrders.add(new AttachmentOrder("EXTR", "right", true));
		atcOrders.add(new AttachmentOrder("SCR", "left", true));
		for (AttachmentOrder atcOrder: atcOrders) {
			atcOrderMap.put(atcOrder.getKey(), atcOrder);
		}
		List<CanonicalOrder> canOrders = new LinkedList<CanonicalOrder>();
		canOrders.add(new CanonicalOrder("VM", "k1", "left", new String[] {}, new String[] {"k1s"}));
		canOrders.add(new CanonicalOrder("VM", "k1s", "left", new String[] {"k1"}, new String[] {}));
		canOrders.add(new CanonicalOrder("VM", "k4", "left", new String[] {"k1", "k1s"}, 
				new String[] {"k2", "k2s", "pof", "lwg__neg", "fragof"}, true));
		canOrders.add(new CanonicalOrder("VM", "k2", "left", new String[] {"k1", "k1s", "k4"}, 
				new String[] {"k2s", "pof", "lwg__neg", "fragof"}, true));
		canOrders.add(new CanonicalOrder("VM", "k2s", "left", new String[] {"k1", "k1s", "k4", "k2"}, 
				new String[] {"pof", "lwg__neg", "fragof"}, true));
		canOrders.add(new CanonicalOrder("VM", "pof", "left", new String[] {"k1", "k1s", "k4", "k2", "k2s"},
				new String[] {"lwg__neg", "fragof"}, true));
		for (CanonicalOrder canOrder: canOrders) {
			canOrderMap.put(canOrder.getKey(), canOrder);
		}
	}
	public boolean violatesHeadOrder(Token dep, Token head, Tree ds) {
		String relType = dep.getDSRelation();
		String headTag = head.getTag();		
		String key = relType + "__" + headTag;
		boolean violatesOrder = false;
		if (canOrderMap.containsKey(key)) {
			violatesOrder = canOrderMap.get(key).violatesHeadOrder(dep, head, ds);
		}
		return violatesOrder;
	}
	public boolean violatesArgumentOrder(Token dep, Token head, Tree ds) {
		String relType = dep.getDSRelation();
		String headTag = head.getTag();		
		String key = relType + "__" + headTag;
		boolean violatesOrder = false;
		if (canOrderMap.containsKey(key)) {
			violatesOrder = canOrderMap.get(key).violatesArgumentOrder(dep, head, ds);
		}
		return violatesOrder;
	}
	public boolean violatesAdjacency(Token dep, Token head, Tree ds) {
		String relType = dep.getDSRelation();
		String headTag = head.getTag();
		String key = relType + "__" + headTag;
		boolean violatesAdjacency = false;
		if (canOrderMap.containsKey(key)) {
			violatesAdjacency = canOrderMap.get(key).violatesAdjacency(dep, head, ds);
		}
		return violatesAdjacency;
	}
	
	public void attachEmtpyCategory(Token ec, Token head, Tree ds) {
		String relType = ec.getDSRelation();
		String headTag = head.getTag();		
		if (canOrderMap.containsKey(relType + "__" + headTag)) {
			canOrderMap.get(relType + "__" + headTag).attachEmptyCategory(ec, head, ds);
		} else if (atcOrderMap.containsKey(ec.getValue("ectype"))) {
			atcOrderMap.get(ec.getValue("ectype")).attachEmptyCategory(ec, head, ds);
		}
		else {
			OrderUtils.attachAdjacentToHeadSpan(ec, head, ds, "left");
		}
	}
	
}
